﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using CutAways.Illustration;
using CutAways.Geometry;

namespace CutAways.Modeling
{
	[Serializable]
	//[DefaultProperty("RenderMode")]
	class GeometricObject
	{
		private string name = "";
		private int index;
		private Triangle[] triangles = null;
		
		private bool visible = true;
		private RenderMode renderMode = RenderMode.Usually;

		private AxesBoundingBox axesBoundingBox;
		private OrientedBoundingBox orientedBoundingBox;
		private BoundingTube boundingTube;

		private CuttingType cuttingType;

		private Interval[] cuttingParametres;

		// ===================================================================

		[Description("Typ rezani objektu")]
		public CuttingType CuttingType
		{
			get { return cuttingType; }
			set { cuttingType = value; }
		}

		[Browsable(false)]
		public Interval[] CuttingParametres
		{
			get { return cuttingParametres; }
			set { cuttingParametres = value; }
		}

		[Browsable(false)]
		public AxesBoundingBox AxesBoundingBox
		{
			get { return axesBoundingBox; }
			set { axesBoundingBox = value; }
		}

		[Browsable(false)]
		public OrientedBoundingBox OrientedBoundingBox
		{
			get { return orientedBoundingBox; }
			set { orientedBoundingBox = value; }
		}

		[Browsable(false)]
		public BoundingTube BoundingTube
		{
			get { return boundingTube; }
			set { boundingTube = value; }
		}
		
		
		[Description("Nazev objektu")]
		public string Name
		{
			get { return name; }
			set { name = value; }
		}
		
		[Browsable(false)]
		public Triangle[] Triangles
		{
			get { return triangles; }
			set { triangles = value; }
		}

		[Browsable(false)]
		public bool Visible
		{
			get { return visible; }
			set { visible = value; }
		}

		[Description("Zpusob vykresleni objektu")]
		public RenderMode RenderMode
		{
			get { return renderMode; }
			set { renderMode = value; }
		}

		[Description("Cislo objektu. Pomoci neho ho lze identifikovat uvnitr modelu.")]
		public int Index
		{
			get { return index; }
		}

		public override string ToString()
		{
			return this.name;
		}

		public GeometricObject(int index)
		{
			this.index = index;
			this.axesBoundingBox = null;
			this.orientedBoundingBox = null;
			this.boundingTube = null;
			this.cuttingType = CuttingType.Box;
			this.cuttingParametres = null;
		}

		public void CreateBoundingBox(Model myModel)
		{
			if (myModel == null || myModel.Vertices.Length == 0)
				throw new Exception("Model is empty.");

			Vector min = myModel.Vertices[triangles[0].VertexIndices[0]];
			Vector max = min;

			foreach (Triangle t in triangles) // takhle spoctu Axis bounding box
			{
				foreach (int index in t.VertexIndices)
				{
					Vertex v = myModel.Vertices[index];
					if (v.X > max.X)
						max.X = v.X;
					else if (v.X < min.X)
						min.X = v.X;
					if (v.Y > max.Y)
						max.Y = v.Y;
					else if (v.Y < min.Y)
						min.Y = v.Y;
					if (v.Z > max.Z)
						max.Z = v.Z;
					else if (v.Z < min.Z)
						min.Z = v.Z;
				}
			}
			this.axesBoundingBox = new AxesBoundingBox(min, max);
			this.orientedBoundingBox = OrientedBoundingBox.ConvertFrom(this.axesBoundingBox, axesBoundingBox.Center);
			this.boundingTube = BoundingTube.ConvertFrom(this.orientedBoundingBox);
		}

		public void CreateOrientedBoundingBox(Model myModel)
		{
			if (myModel == null || myModel.Vertices.Length == 0)
				throw new Exception("Model is empty.");

			Vector center = axesBoundingBox.Center;
			Vector min = Vector.RotateAllAxisReversed(myModel.Vertices[triangles[0].VertexIndices[0]] - center, axesBoundingBox.RotationAngles);
			Vector max = min;

			foreach (Triangle t in triangles) // takhle spoctu Axis bounding box
			{
				foreach (int index in t.VertexIndices)
				{
					Vector v = Vector.RotateAllAxisReversed(myModel.Vertices[index] - center, axesBoundingBox.RotationAngles);
					if (v.X > max.X)
						max.X = v.X;
					else if (v.X < min.X)
						min.X = v.X;
					if (v.Y > max.Y)
						max.Y = v.Y;
					else if (v.Y < min.Y)
						min.Y = v.Y;
					if (v.Z > max.Z)
						max.Z = v.Z;
					else if (v.Z < min.Z)
						min.Z = v.Z;
				}
			}

			min += center;
			max += center;

			AxesBoundingBox abb = new AxesBoundingBox(min, max);
			this.orientedBoundingBox = OrientedBoundingBox.ConvertFrom(abb, center);
			this.orientedBoundingBox.Rotate(center, -axesBoundingBox.RotationAngles);
		}

		public void CreateBoundingTube(Model myModel)
		{
			this.boundingTube = BoundingTube.ConvertFrom(this.orientedBoundingBox);

			if (myModel == null || myModel.Vertices.Length == 0)
				throw new Exception("Model is empty.");

			Vector center = axesBoundingBox.Center;
			float radius = float.MinValue;

			Vector back = Vector.RotateAllAxisReversed(boundingTube.BackCenter - center, axesBoundingBox.RotationAngles) + center;
			Vector front = Vector.RotateAllAxisReversed(boundingTube.FrontCenter - center, axesBoundingBox.RotationAngles) + center;

			foreach (Triangle t in triangles) // takhle spoctu Axis bounding box
			{
				foreach (int index in t.VertexIndices)
				{
					Vector v = Vector.RotateAllAxisReversed(myModel.Vertices[index] - center, axesBoundingBox.RotationAngles) + center;
					float r = v.DistanceFromLine(back, front);
					if (r > radius)
						radius = r;
				}
			}
			boundingTube.SetRadius(radius);
		}

		public void ComputeCuttingParametres(GeometricObject selected, Camera viewpoint)
		{
			switch (this.cuttingType)
			{
				case CuttingType.Box:
					this.cuttingParametres = this.orientedBoundingBox.IntersectionParametres(selected.orientedBoundingBox, viewpoint.Eye);
					break;
				case CuttingType.WedgeTube:
					this.cuttingParametres = this.boundingTube.IntersectionParametres(selected.orientedBoundingBox, viewpoint.Eye);
					break;
				default:
					this.cuttingParametres = null;
					throw new NotSupportedException("This cutting type is not supported.");
			}
		}

		public void DrawBoundingBox()
		{
			if (this.cuttingType == CuttingType.WedgeTube)
				this.boundingTube.Draw();
			else
				this.orientedBoundingBox.Draw();
		}

        public OrientedBoundingBox GetBoundingBoxCut()
        {
            if (this.cuttingType != CuttingType.Box)
                return null;

            Vector u = orientedBoundingBox.Points[1] - orientedBoundingBox.Points[0];
            Vector v = orientedBoundingBox.Points[4] - orientedBoundingBox.Points[0];
            Vector w = orientedBoundingBox.Points[2] - orientedBoundingBox.Points[0];
            Vector o = orientedBoundingBox.Points[0] + u * cuttingParametres[0].Minimum + v * cuttingParametres[1].Minimum + w * cuttingParametres[2].Minimum;

            OrientedBoundingBox cut = new OrientedBoundingBox();
            cut.Points[0] = o;
            cut.Points[1] = o + u * cuttingParametres[0].Length;
            cut.Points[2] = o + w * cuttingParametres[2].Length;
            cut.Points[3] = o + u * cuttingParametres[0].Length + w * cuttingParametres[2].Length;
            cut.Points[4] = o + v * cuttingParametres[1].Length;
            cut.Points[5] = o + u * cuttingParametres[0].Length + v * cuttingParametres[1].Length;
            cut.Points[6] = o + v * cuttingParametres[1].Length + w * cuttingParametres[2].Length;
            cut.Points[7] = o + u * cuttingParametres[0].Length + v * cuttingParametres[1].Length + w * cuttingParametres[2].Length;

            return cut;
        }

		public void DrawCut()
		{
			if (this.cuttingParametres == null)
				return;
			if (this.cuttingType == CuttingType.Box)
			{
                OrientedBoundingBox cut = GetBoundingBoxCut();
				cut.Draw();
			}
			else if (this.cuttingType == CuttingType.WedgeTube)
			{
				if (this.cuttingParametres[0] != Interval.ZeroZero && this.cuttingParametres[1] != Interval.ZeroZero)
					this.boundingTube.Draw(this.cuttingParametres[0], this.cuttingParametres[1]);
			}
		}
	}
}
